// DriveNotes. Copyright (c) 2011, Andrei Senchuk. All rights reserved.
package net.taviscaron.drivenotes.server.service;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.TreeMap;
import net.taviscaron.drivenotes.server.model.dao.CarDAO;
import net.taviscaron.drivenotes.server.model.dao.ChargingDAO;
import net.taviscaron.drivenotes.server.model.dao.ServiceDAO;
import net.taviscaron.drivenotes.shared.model.entity.Car;
import net.taviscaron.drivenotes.shared.model.entity.Charging;
import net.taviscaron.drivenotes.shared.model.entity.Service;
import net.taviscaron.drivenotes.shared.service.StatisticService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.math.MathException;
import org.apache.commons.math.analysis.UnivariateRealFunction;
import org.apache.commons.math.analysis.interpolation.SplineInterpolator;
import org.apache.commons.math.analysis.interpolation.UnivariateRealInterpolator;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.transaction.annotation.Transactional;

/**
 * Statistic service impl
 * @author Andrei Senchuk
 */
public class StatisticServiceImpl implements StatisticService {
    private static final Log logger = LogFactory.getLog(StatisticServiceImpl.class);

    private static final int FUEL_CONSUMPTION_TENDENTION_ACCURANCY = 25;
    private static final int MIN_CHARGINGS_TO_CALCULATE_TREND = 4;

    private CarDAO carDAO;
    private ServiceDAO serviceDAO;
    private ChargingDAO chargingDAO;

    @Required
    public void setCarDAO(CarDAO carDAO) {
        this.carDAO = carDAO;
    }

    @Required
    public void setChargingDAO(ChargingDAO chargingDAO) {
        this.chargingDAO = chargingDAO;
    }

    @Required
    public void setServiceDAO(ServiceDAO serviceDAO) {
        this.serviceDAO = serviceDAO;
    }

    @Transactional
    public Float calculateTotalServiceCost(Car car) {
        List<Service> services = serviceDAO.findServicesByCar(car);
        Float cost = 0f;
        for (Service service : services) {
            cost += service.getSparesCost();
            if(service.getServiceCost() != null) {
                cost += service.getServiceCost();
            }
        }
        return cost;
    }

    @Transactional
    public Float calculateTotalChargingCost(Car car) {
        List<Charging> chargings = chargingDAO.findChargingsByCar(car);
        Float cost = 0f;
        for (Charging charging : chargings) {
            cost += charging.getPrice() * charging.getVolume();
        }
        return cost;
    }

    @Transactional
    public TreeMap<Integer, Double> calculateFuelConsumptionTendention(Car car) {
        List<Charging> chargings = chargingDAO.findChargingsByCar(car);
        int count = chargings.size();
        
        if(count < MIN_CHARGINGS_TO_CALCULATE_TREND) {
            return null; // can't interpolate
        }

        Collections.sort(chargings, new Comparator<Charging>() {
            public int compare(Charging o1, Charging o2) {
                return (o1.getOdometrValue() - o2.getOdometrValue());
            }
        });

        double[] mileage = new double[count - 1];
        double[] consumption = new double[count - 1];
        
        for(int i = 0; i < count - 1; i++) {
            Charging previous = chargings.get(i);
            Charging current = chargings.get(i + 1);
            
            mileage[i] = previous.getOdometrValue();
            int length = current.getOdometrValue() - previous.getOdometrValue();
            double volume = previous.getVolume();
            consumption[i] = volume / length * 100; // litrage per 100 km/miles/etc
        }

        UnivariateRealInterpolator interpolator = new SplineInterpolator();
        UnivariateRealFunction function;

        try {
            function = interpolator.interpolate(mileage, consumption);

            TreeMap<Integer, Double> fuelConsumptionTendention = new TreeMap<Integer, Double>();
            int min = (int)mileage[0];
            int max = (int)mileage[mileage.length - 1];
            int delta = (max - min) / FUEL_CONSUMPTION_TENDENTION_ACCURANCY;

            if(delta == 0) {
                return null;
            }

            for(int i = min; i < max; i += delta) {
                fuelConsumptionTendention.put(i, function.value(i));
            }
            
            return (fuelConsumptionTendention.size() > 1) ? fuelConsumptionTendention : null;
        } catch (MathException e) {
            logger.warn("Math exception at fuel consumption interpolation", e);
            return null;
        }
    }
}
